package com.xh.core.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.xh.core.bean.BaseRequestBean;
import com.xh.core.bean.XhExportHeaderBean;
import com.xh.core.bean.XhPage;
import com.xh.core.event.ExportExcelEvent;
import com.xh.core.model.SuperEntity;
import com.xh.core.properties.XhProperties;
import com.xh.core.service.IBaseService;
import com.xh.core.util.DateUtils;
import com.xh.core.util.ReflectUtils;
import com.xh.core.util.StringUtils;
import lombok.Data;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;

@Data
public class BaseServiceImpl<M extends BaseMapper<T>, T> implements IBaseService<T> {

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    private XhProperties xhProperties;

    @Autowired
    public M dao;

    protected T model;


    public BaseServiceImpl() {
        try {
// 通过反射获取model的真实类型
            ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
            Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[1];
// 通过反射创建model的实例
            model = clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void excludeColumns(QueryWrapper qw, String... columns) {
        qw.setEntity(model);
        qw.excludeColumns(columns);
    }


    @Override
    public T getById(Serializable id) {
        return dao.selectById(id);
    }

    @Override
    public <S> S getById(Serializable id, Class<S> clazz) {
        T obj = dao.selectById(id);
        try {
            if (obj == null) {
                return null;
            }
            S target = clazz.newInstance();
            BeanUtils.copyProperties(obj, target);
            return target;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public T selectFirst(Wrapper w) {
        XhPage<T> page = new XhPage<>();
        page.setSize(1);
        page.setCurrent(1);

        page = (XhPage<T>) this.dao.selectPage(page, w);
        T obj = page.getTotal() > 0 ? (T) page.getRecords().get(0) : null;
        if (obj == null) {
            return null;
        }
        return obj;
    }

    @Override
    public <S> S selectFirst(Wrapper<T> w, Class<S> clazz) {
        XhPage<T> page = new XhPage<>();
        page.setSize(1);
        page.setCurrent(1);

        page = (XhPage<T>) this.dao.selectPage(page, w);
        T obj = page.getTotal() > 0 ? (T) page.getRecords().get(0) : null;
        if (obj == null) {
            return null;
        }
        try {
            S target = clazz.newInstance();
            BeanUtils.copyProperties(obj, target);
            return target;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Integer selectCount(Wrapper<T> w) {
        return this.dao.selectCount(w);
    }

    @Override
    public <S> S selectOne(Wrapper<T> w, Class<S> clazz) {
        T obj = this.dao.selectOne(w);
        try {
            if (obj == null) {
                return null;
            }
            S target = clazz.newInstance();
            BeanUtils.copyProperties(obj, target);
            return target;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <S> List<S> selectList(Wrapper<T> w, Class<S> clazz) {
        List result = new ArrayList();
        List<Map<String, Object>> list = this.dao.selectMaps(w);
        for (Map<String, Object> map : list) {
            result.add(ReflectUtils.convertMap2Object(map, clazz));
        }
        return result;
    }

    @Override
    public <S> IPage<S> selectPage(IPage page, Wrapper<T> w, Class<S> clazz) {
        List<S> result = new ArrayList();
        IPage iPage = this.dao.selectMapsPage(page, w);
        List<Map<String, Object>> records = iPage.getRecords();
        if (!CollectionUtils.isEmpty(records)) {
            for (Map<String, Object> map : records) {
                result.add(ReflectUtils.convertMap2Object(map, clazz));
            }
        }
        iPage.setRecords(result);
        return iPage;
    }

    @Override
    public boolean insert(T entity) {
        int count = dao.insert(entity);
        return true;
    }

    @Override
    public void insertObj(Object object) {
        try {
            Class<?> aClass = model.getClass();
            T entity = (T) aClass.newInstance();
            BeanUtils.copyProperties(object, entity);
            dao.insert(entity);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean batchInsert(List<T> list) {
        for (T t : list) {
            int count = dao.insert(t);
        }
        return true;
    }

    @Override
    public boolean batchInsertObj(List<Object> objects) {
        for (Object object : objects) {
            insertObj(object);
        }
        return true;
    }

    @Override
    public boolean update(T entity) {
        int count = dao.updateById(entity);
        return true;
    }

    @Override
    public boolean updateObj(Object o) {
        try {
            Class<?> aClass = model.getClass();
            T entity = (T) aClass.newInstance();
            BeanUtils.copyProperties(o, entity);
            dao.updateById(entity);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    @Override
    public boolean update(T updateEntity, Wrapper<T> whereWrapper) {
        Integer count = dao.update(updateEntity, whereWrapper);
        return true;
    }

    @Override
    public boolean batchUpdate(List<T> list) {
        for (T t : list) {
            dao.updateById(t);
        }
        return true;
    }

    @Override
    public boolean batchUpdateObj(List<Object> objects) {
        for (Object object : objects) {
            updateObj(object);
        }
        return true;
    }

    @Override
    public boolean delete(Serializable id) {
        int count = dao.deleteById(id);
        return true;
    }

    @Override
    public boolean batchDelete(Serializable[] ids) {
        int count = dao.deleteBatchIds(Arrays.asList(ids));
        return true;
    }

    @Override
    public boolean deleteByCondition(QueryWrapper<T> qw) {
        dao.delete(qw);
        return true;
    }

    @Override
    public List<T> queryAll() {
        return dao.selectList(new QueryWrapper<T>());
    }

    @Override
    public List<T> queryByCondition(QueryWrapper<T> qw) {
        return dao.selectList(qw);
    }

    @Override
    public Workbook exportAll(LinkedHashMap<String, XhExportHeaderBean> exportHeaderBeans, BaseRequestBean param, String queryPageMethodStr) throws Exception {
        List<ExcelExportEntity> entity = new ArrayList<ExcelExportEntity>();
        int i = 0;
        for (String col : exportHeaderBeans.keySet()) {
            XhExportHeaderBean exportHeaderBean = exportHeaderBeans.get(col);
            ExcelExportEntity excelExportEntity = new ExcelExportEntity(exportHeaderBean.getDisplayName(), col);
            entity.add(excelExportEntity);
            i++;
        }

        Method queryPageMethod = this.getClass().getDeclaredMethod(queryPageMethodStr, XhPage.class, param.getClass());
        XhPage page = new XhPage();
        page.setSize(3000);
        XhPage pageResult = (XhPage) queryPageMethod.invoke(this, page, param);

        if (pageResult.getTotal() > 0) {//有数据
            Workbook workbook = null;
            ExportParams exportParams = new ExportParams();
            //导出第一页内容，创建excel
            //构造导出格式数据
            List<Map<String, Object>> list = convertDataList(exportHeaderBeans, pageResult);
            workbook = ExcelExportUtil.exportBigExcel(exportParams, entity, list);
            //导出后续内容
            if (pageResult.getPages() > 1) {
                for (int currentPage = 2; currentPage <= pageResult.getPages(); currentPage++) {
                    page = new XhPage();
                    page.setCurrent(currentPage);
                    page.setSize(3000);
                    pageResult = (XhPage) queryPageMethod.invoke(this, page, param);
                    if (pageResult.getRecords().size() > 0) {
                        //附加数据到excel
                        list = convertDataList(exportHeaderBeans, pageResult);
                        workbook = ExcelExportUtil.exportBigExcel(exportParams, entity, list);
                    }
                }
            }
            ExcelExportUtil.closeExportBigExcel();
            return workbook;
        } else {

        }
        return null;
    }

    @Override
    public String exportAll(HttpServletRequest request, LinkedHashMap<String, XhExportHeaderBean> exportHeaderBeans, BaseRequestBean param, String queryPageMethodStr) throws Exception {
        String exportId = UUID.randomUUID().toString().replaceAll("-", "");
        //applicationContext.publishEvent(new ExportExcelEvent(exportId,this,request,0));
        Workbook workbook = this.exportAll(exportHeaderBeans, param, queryPageMethodStr);
        if (workbook != null) {
            String exportPath = request.getSession().getServletContext().getRealPath("") + getXhProperties().getPath().getExport() + "/";
            String fileName = DateTime.now().toString("yyyyMMddHHmmssSSS") + ".xlsx";
            File dir = new File(exportPath);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(exportPath + fileName);
            workbook.write(fos);
            fos.close();
            String url = getXhProperties().getPath().getExport() + "/" + fileName;
            applicationContext.publishEvent(new ExportExcelEvent(exportId, this, request, 1, url));
            return url;
        }
        //applicationContext.publishEvent(new ExportExcelEvent(exportId,this,request,-1));
        return null;
    }

    /**
     * 转换结果集到导出列表对象
     *
     * @param exportHeaderBeans
     * @param pageResult
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private List<Map<String, Object>> convertDataList(LinkedHashMap<String, XhExportHeaderBean> exportHeaderBeans, XhPage pageResult) throws NoSuchFieldException, IllegalAccessException {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (Object item : pageResult.getRecords()) {//循环查询结果
            Map<String, Object> exportItem = new HashMap<>();//导出格式对象
            int colIndex = 0;
            for (String col : exportHeaderBeans.keySet()) {
                Object value = null;
                if (item instanceof Map) {//判断结果集是否为Map类型
                    value = ((Map) item).get(col);
                } else if (item instanceof SuperEntity) {//判断结果集是否为实体对象
                    Field field = null;
                    try {
                        field = item.getClass().getDeclaredField(col);//先从试题中找私有属性
                    } catch (NoSuchFieldException ex) {
                        field = item.getClass().getSuperclass().getDeclaredField(col);//实体中无法找到则从父类找公共属性
                    }
                    field.setAccessible(true);
                    value = field.get(item);
                } else if (item instanceof Object[]) {
                    value = ((Object[]) item)[colIndex];
                }
                value = convertValue(col, value, exportHeaderBeans.get(col));
                exportItem.put(col, value);
                colIndex++;
            }
            list.add(exportItem);
        }
        return list;
    }

    /**
     * 常量枚举值字段转换
     *
     * @param col
     * @param value
     * @param exportHeaderBean
     * @return
     */
    private Object convertValue(String col, Object value, XhExportHeaderBean exportHeaderBean) {
        if (value == null) {
            return "";
        }
        if (exportHeaderBean.getRenderCallback() != null) {
            return exportHeaderBean.getRenderCallback().render(value);
        }
        if (exportHeaderBean.getConstants() != null) {
            return exportHeaderBean.getConstants().get(value);
        }
        if (!StringUtils.isEmpty(exportHeaderBean.getDateFormat())) {
            return DateUtils.format((Date) value, exportHeaderBean.getDateFormat());
        }

        return value;
    }


    static void getClassFieldAndMethod(Class cur_class) {
        String class_name = cur_class.getName();
        Field[] obj_fields = cur_class.getDeclaredFields();
        for (Field field : obj_fields) {
            field.setAccessible(true);
            System.out.println(class_name + ":" + field.getName());
        }
        Method[] methods = cur_class.getDeclaredMethods();
        for (Method method : methods) {
            method.setAccessible(true);
            System.out.println(class_name + ":" + method.getName());
        }
        if (cur_class.getSuperclass() != null) {
            getClassFieldAndMethod(cur_class.getSuperclass());
        }
    }

}
